var directionDisplay;
var directionsService;// = new google.maps.DirectionsService();
var map;
var num_waypoints = 0;
var alpha = 0.5;
var geocoder;

var current;
var address_list;
var pickups;
var dropoffs;
var sizes;
var waits;

var json_addresses;

var matrix_cache; 

var draw_map; // decide whether or not the map needs to be redrawn
var busy = false; // is this used anytmore?

var edit_cache = false; // test hack

var outputDiv;

var body_container;
var title_container;
var header_container;
var map_container;
var console_container;

var consoleList; 
var MAX_CONSOLE_LENGTH = 500;

var subconsoleList;
var MAX_SUBCONSOLE_LENGTH = 50;

var van;

var next_destination_id;

var refresh_timeout = false;
var refresh_timeout_called = false;

var routeList;

var watching_position = false;

var car_marker;

var hack_van_row;
var hacked_van = false;

// automatically scroll to bottom of console when new content is added
// this is always true unless the mouse is hovering over the console
var console_autoscroll = true;

var pending_operations = [];

// called when the page loads; set up map and compute route
function initialize(vanValue) {
	
	_disable_controls();
	var wburg = new google.maps.LatLng(37.2710135, -76.707457);
	var myOptions = {
		zoom: 15,
		mapTypeId: google.maps.MapTypeId.ROADMAP,
		center: wburg
	}
	
	map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	geocoder = new google.maps.Geocoder();
	directionsDisplay = new DirectionsDisplay(map, document.getElementById("directions_panel"));
	directionsService = new DirectionsRoute(directionsDisplay);

	outputDiv = document.getElementById('outputDiv');
	suboutputDiv = document.getElementById('suboutputDiv');

	body_container = document.getElementsByTagName("body")[0];
	title_container = document.getElementById('title_container');
	header_container = document.getElementById('header_container');
	map_container = document.getElementById('map_canvas');
	console_container = document.getElementById('outputDiv_container');

	consoleList = [];
	subconsoleList = [];
	van = vanValue;
	draw_map = true;
	check_refresh();
	set_heights();

	$("#suboutputDiv_container").hover(
		function () {
			console_autoscroll = false;
		},
		function () {
			console_autoscroll = true;
		}
	);

	window.onerror = function(){
		alert('Something broke :(');
	}
	
	$.ui.autocomplete.prototype._renderItem = function( ul, item){

		//var items = item.label.split('|'); // name|core
		var core = item.core;

		var term = this.term.split(' ').join('|');
		
		var re = new RegExp("(" + term + ")", "gi") ;
		var t = item.value.replace(re,"<strong>$1</strong>");

		if (core == '1') {
			t = "<em>"+t+"</em>";
		}

		return $( "<li></li>" )
			.data( "item.autocomplete", item )
			.append( "<a>" + t + "</a>" )
			.appendTo( ul );
	};

	$(function() {
		$("#pickup, #dropoff" ).autocomplete({
			source: "location_suggest.php",
			dataType: 'json',
			minLength: 1
		});
	});
}

var van_id;
var refreshing = false;
var last_updated = -1;

// check cache for updates at regular intervals
function check_refresh(cheat) {

	if (refreshing) {
		_write_console("Refresh canceled. ");
		return;
	}

	if (refresh_timeout) {
		clearTimeout(refresh_timeout);
		_write_subconsole('clear timeout: '+refresh_timeout);
		refresh_timeout = false;
	}

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {

			var response = xmlhttp.responseText;
			if (parseFloat(response) != last_updated) { // out of date
				_write_console('cache out of date. time to update it.');
				last_updated = parseFloat(response);
				draw_map = true;
				refresh(cheat); // perform refresh
				
			} else { // up to date
				if (pending_operations.length > 0) {
					_write_console('Cache up to date: Found a pending operation in stack! Time to call it. ');
					var op = pending_operations.pop();
					op();
				} else {
					refresh_timeout = setTimeout(function(){check_refresh();},5000); // schedule next refresh check
					_write_subconsole('check refresh response: '+response);
					_write_subconsole('check refresh_timeout: '+refresh_timeout);
				}
			}
		}
	}

	var params = "fname=route_cache_"+van;
	//var params = "fname=last_updated";
	
	xmlhttp.open("POST","get_last_updated.php");
	xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xmlhttp.send(params);
}

// outdated cache detected at this point; perform the refresh
function refresh(refresh_lock_opened) {

	refreshing = true;
	refresh_timeout_called = true;

	if (!refresh_lock_opened) {
		// to refresh, lock_van must not be present
		_write_subconsole("going to check lock for refresh now.");
		
		check_set_lock(function(){populateVars(false);}, [{"name": "lock_van", "van": van}], "lock_refresh", '');
		return;
	} else {
		_write_subconsole("lock opened for refresh: "+refresh_timeout);
	}

	//lock(function(){populateVars(false);}, 'lock_refresh', '');
	check_set_lock(function(){populateVars(false);}, [], "lock_refresh", '');
}

// here's a concurrency dilemma 
// user 1 loads page
// user 1 is in the middle of reloading
// user 1 has loaded json_addresses but not matrix_cache
// user 2 performs an insertion
// user 2 causes an update in matrix_cache
// user 1 sees new matrix_cache but still has old json_addresses
// user 1 therefore thinks the cache is out-of-date and deletes the new address(s) from cache
// user 1 and user 2 are confused

// that should be fixed, but it's a good test case to be aware of


var exit_called = false;

// read pickup/dropoff info from db
function populateVars(can_update, is_insert) {
	
	refreshing = true;

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var result = xmlhttp.responseText;

			json_addresses = JSON.parse(result);

			// hack 
			var need_hack = true; // hack needed if db contains no van entry
			van_id = 0;
			for (id in json_addresses.rows) {
				if (json_addresses.rows[id].type == 'VAN' && json_addresses.rows[id].van == van) {
					need_hack = false;
					van_id = id;
					break;
				}
			}

			if (need_hack) {

				_write_console("Loaded json_addresses of length "+(json_addresses.length+1)+".");

				if (hacked_van) {

					json_addresses.rows[0] = hack_van_row;
					json_addresses.length++;
					populateVars_exit(can_update, is_insert);

				} else if(navigator.geolocation) {

					hack_van_row = {"id":0,"assoc_id":"None","van":van,"type":"VAN",
										  "address":"Williamsburg, VA, USA","num_people":"0",
										  "total_wait":"0","start_time": (Math.round(+new Date()/1000)),
										  "last_updated": (Math.round(+new Date()/1000))};

					json_addresses.rows[0] = hack_van_row;
					json_addresses.length++;

					exit_called = false;

					navigator.geolocation.getCurrentPosition(function(position){

						//clearTimeout(exit_timeout);
						// if (exit_called) {
						// 	alert('NO! You were too late. You can refresh though. ');
						// 	return;
						// }
						onPositionUpdate(position, function(){
							
							populateVars_exit(can_update, is_insert);

					})}, function(error){

						//clearTimeout(exit_timeout);
						// if (exit_called) {
						// 	alert('NO! You were too late. ');
						// 	return;
						// }
						populateVars_exit(can_update, is_insert);

					}, {
						enableHighAccuracy:true,
						timeout: 15000,
						maximumAge:Infinity
					});
				} else {
					alert("navigator.geolocation is not available");
				}
			} else {
				_write_console("Loaded json_addresses of length "+(json_addresses.length)+".");
				populateVars_exit(can_update, is_insert);
			}
		}
	}
	
	xmlhttp.open("GET","command.php?van="+van);
	xmlhttp.send();
}

function populateVars_exit(can_update, is_insert){

	//exit_called = true;

	if (can_update) {
		// read_matrix_cache --> check_update_cache --> construct_python --> optimize_route 
		// --> cache_route, plot_route, load_locations
		read_matrix_cache(can_update, is_insert);
		_write_subconsole('uncached load.');
	} else {
		read_python_cache();
		_write_subconsole('cached load.');
	}
}

// use cache as is
function read_python_cache() {
	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var route = xmlhttp.responseText;

				if (route != "0") {
					route = van_id+"," + route;
				} else {
					route = ""+van_id;
				}
				routeList = route.split(",");

				// \begin{stupidshit}
				var num_locations = 0;
				for (id in json_addresses.rows) {
					num_locations += json_addresses.rows[id].van == van;
				}
				// \end{stupidshit}

				if (routeList.length != num_locations) {
					alert('bad python cache?');
					_write_console("python cache: "+routeList.length +"; locations db: "+num_locations);
				} 

				plot_route(routeList);
				load_locations(routeList, false);
				draw_map = false;
				busy = false;
		}
	}
	
	xmlhttp.open("GET","cache.php?fname=route_cache_"+van+"&type=python");
	xmlhttp.send();
}

// distance matrix cache
function read_matrix_cache(can_update, is_insert, new_ids) {

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var result = xmlhttp.responseText;
			if (JSON.stringify(matrix_cache) == result) {
				
			} else {
				matrix_cache = JSON.parse(result);
				draw_map = true;
			}
			// currently, the matrix cache is based on data that can only be modified
			// by this file, and when that modification happens, the cache (which is stored
			// on the server) is updated at the same time. therefore, it is safe 
			// at this point to assume that the cache is up-to-date. however, if future
			// design modifications violate that assumption, then this code will 
			// behave unexpectedly. this is poor design; the cache really should be 
			// checked to see if updates are needed. but that is difficult/impossible
			// to do ...for various reasons, but primarily due to Maps API limitations. 

			if (draw_map)
				_write_subconsole(_response_table(matrix_cache));
			// Disregard the above. Here we update the cache if need be. 
			check_update_cache(can_update, is_insert, new_ids);
		}
	}
	
	xmlhttp.open("GET","cache.php?fname=matrix_cache_"+van+"&type=matrix&address="
					+encodeURIComponent(json_addresses.rows[van_id].address));
	xmlhttp.send();
}

var perm_array; // is this even used?
var add_perm_array; // index in new_addresses --> index in json_addresses
var id_map; // id in json_addresses --> index in matrix_cache
//var reverse_id_map; // index in matrix_cache --> id in json_addresses

// compare json_addresses to cache contents
function check_update_cache(can_update, is_insert, new_ids) {
	
	if (!can_update) {
		_write_console("skipping cache update.");
		construct_python();
		return;
	}
	
	var new_addresses = [];
	var obsolete_address_count = 0;
	var temp_address_list = [];
	var temp_latlng_list = [];
	add_perm_array = []; 

	// find addresses in json_addresses not in matrix_cache
	// need to add to matrix_cache
	
	for (var id in json_addresses.rows) {
		if (json_addresses.rows[id].van == van) { // only include locations for this van
			if (matrix_cache.originAddresses.indexOf(json_addresses.rows[id].address) == -1) {
				new_addresses.push(json_addresses.rows[id].address); // ?????????
				
				//new_addresses.push(new google.maps.LatLng(json_addresses.rows[id].lat, json_addresses.rows[id].lng));
				
				add_perm_array.push(parseFloat(id));
			}
			
			temp_address_list.push(json_addresses.rows[id].address);
			//temp_latlng_list.push(new google.maps.LatLng(json_addresses.rows[id].lat, json_addresses.rows[id].lng));
		}
	}

	if (new_ids != undefined) {
		for (var i = 0; i < new_ids.length; i++) {
			if (add_perm_array.indexOf(parseFloat(new_ids[i])) == -1) {// stupid hack
				//_write_console('Stupid hack insert: '+JSON.stringify(json_addresses.rows[new_ids[i]]));
				//_write_console('IS_INSERT: '+is_insert);
				_insertDBLocation(json_addresses.rows[new_ids[i]]);
			}
		}
	}

	// find addresses in matrix_cache not in json_addresses
	// need to remove it from matrix_cache
	// (pray that there's only one)
	if (new_addresses.length == 0) {
		for (var i = 0; i < matrix_cache.originAddresses.length; i++) { 
			if (temp_address_list.indexOf(matrix_cache.originAddresses[i]) == -1) {
				obsolete_address_count++;
				// update cache
				if (!can_update) {
					alert('want to delete '+matrix_cache.originAddresses[i]+' but can\'t. I will try again...');
					populateVars(false);
					return;
				}
				_delete_location_from_matrix(i);
			}
		}
	}

	// cache has been updated by removing some addresses, so write it and move on
	if (obsolete_address_count > 0) {
		write_cache();
		return; // ?????
	}

	if (obsolete_address_count > 1) {
		alert('This is bad...');
	}

	if (new_addresses.length > 0) { // need to update cache
		if (!can_update) {
			alert('want to add '+new_addresses[0]+' but can\'t. I will try again...');
			populateVars(false);
			return;
		}
		args = [new_addresses, temp_address_list, temp_address_list, new_addresses];
		_write_console('Ready to update matrix cache with: '+JSON.stringify(new_addresses));
		calculateDistances(args, 0, is_insert, new_ids); // construct_python will get called after cache is updated
	} else { // use cache as-is
		construct_python(can_update, is_insert, new_ids);
	}

	outputDiv.innerHTML += "Check cache: Found "+new_addresses.length+
													  " addresses to add and "+obsolete_address_count+
													  " addresses to remove.<br/>";
}

// request distance matrix
// format of args: [origins, destinations, origins, destinations, ...]
// index describes which origins/destinations pair to use
function calculateDistances(args, index, is_insert, new_ids) {
	
	var service = new google.maps.DistanceMatrixService();
	
	_write_console("origins: "+JSON.stringify(args[index]));

	service.getDistanceMatrix(
		{
			origins: args[index],
			destinations: args[index+1],
			travelMode: google.maps.TravelMode.DRIVING,
			unitSystem: google.maps.UnitSystem.METRIC,
			avoidHighways: false,
			avoidTolls: false
		}, function(response, status) {
			callback(response, status, args, index, is_insert, new_ids);
		});
}

// callback to distance matrix request. first merge matrix cache;
// then construct python command string to find optimal route
// format of args: [origins, destinations, origins, destinations, ...]
// expected use of args: [new_addresses, all_addresses, all_addresses, new_addresses]
// (other uses haven't been tested and probably wouldn't work)
function callback(response, status, args, index, is_insert, new_ids) {
	if (status != google.maps.DistanceMatrixStatus.OK) {
		alert('Error was: ' + status);
	} else {
/*
		outputDiv.innerHTML += "Current cache: " + _response_table(matrix_cache) + "<br/><br/>" 
							+ "Merge with: " + _response_table(response) + "<br/><br/>";
*/
		if (matrix_cache.rows.length == 0) { // empty cache
			matrix_cache = response; // populate with results of current response
		} else {
			if (index == 0 && response.originAddresses.length <= 2) { // new locations to everywhere -- new rows

				// first check if all new locations are valid
				// if any single one is invalid, cancel the entire request
				for (var i = 0; i < response.originAddresses.length; i++) {
					if (response.rows[i].elements[0].status != "OK") {
						var bad_address = response.originAddresses[i];
						if (bad_address == '') 
							bad_address = json_addresses.rows[add_perm_array[i]].address;
						alert('I don\'t know how to get to "'+bad_address+
							'", so I\'m canceling the whole request.');
						// have to go through and delete each new location
						for (var j = 0; j < response.originAddresses.length; j++) {
							delete json_addresses.rows[add_perm_array[j]];
						}

						// delete the new locations from db since they're invalid
						$.post("delete.php", 
							   {"ids[]": new_ids},
							   function(response) {
							   		response = response.trim();
							   		if (response != "") {
							   			_write_console("<strong>"+response+"</strong>");
							   		}
							   }
						);

						_enable_controls();
						unlock('lock_van', van);
						return;
					}
				}

				for (var i = 0; i < response.originAddresses.length; i++) {
					//matrix_cache.originAddresses.push(response.originAddresses[i]);
					matrix_cache.originAddresses.push(args[index][i]) // use the original provided address
					matrix_cache.rows.push(response.rows[i]);         // rather than matrix cache's reinterpretation
					_write_console('Distance matrix insert: '+JSON.stringify(json_addresses.rows[add_perm_array[i]]));
					_insertDBLocation(json_addresses.rows[add_perm_array[i]]); // now that we've confirmed 0 errors, ready to insert
				}
			
			} else if (index == 2 && response.destinationAddresses.length <= 2) { // everywhere to new location
				for (var j = 0; j < response.destinationAddresses.length; j++) {  // -- new col
					//matrix_cache.destinationAddresses.push(response.destinationAddresses[j]);
					matrix_cache.destinationAddresses.push(args[index+1][j]);
					for(var i=0; i<matrix_cache.originAddresses.length-response.destinationAddresses.length; i++){
						matrix_cache.rows[i].elements.push(response.rows[i].elements[j]);
					}
				}
			} else {
				alert("Huh? This shouldn't happen. ");
			}
		}
		
		_write_subconsole("Updated cache: " + _response_table(matrix_cache) + "<br/><br/><br/>");

		if (index+2 < args.length) { // continue processing args list
			calculateDistances(args, index+2, is_insert, new_ids);
		} else { // ready to update matrix cache
			write_cache(is_insert, new_ids);
		}
	}
}

// write contents of current cache to server file; then construct_python
function write_cache(is_insert, new_ids) {
	// the cache has been updated, so we will need to redraw the map
	draw_map = true;

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			construct_python(true, is_insert, new_ids); 
		}
	}

	var params = "cache="+encodeURIComponent(JSON.stringify(matrix_cache))+"&fname=matrix_cache_"+van;
	
	xmlhttp.open("POST","write_cache.php");
	xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xmlhttp.send(params);
}

// construct python call from matrix cache, which is assumed up-to-date at this point
function construct_python(can_update, is_insert, new_ids) {

	// so i heard the cache is up-to-date here. good. i will do some unrelated things here then.
	id_map = {};

	for (var id in json_addresses.rows) {
		if (json_addresses.rows[id].van == van)
			id_map[id] = matrix_cache.originAddresses.indexOf(json_addresses.rows[id].address);
			if (id_map[id] == -1) {
				alert('Couldn\'t find '+json_addresses.rows[id].address+' in the matrix cache... Bad things shall ensue.');
			}
	}
	// ---------------------------------------------------------------------------------------------

	var command = 'python ../../route/routing.py ';
	
	var first_id_set = [];

	if (!is_insert)
		command += _partial_command(van_id, first_id_set); 
	else if (next_destination_id != undefined) {/*
		for (var i in json_addresses.rows) {
			if (i != van_id && json_addresses.rows[i].van == van && new_ids.indexOf(parseFloat(i)) == -1) { // non-van, not new id
				if (first_id_set.length == 0 || json_addresses.rows[first_id_set[0]].address == json_addresses.rows[i].address) {
					first_id_set.push(i);
					break;
				}
			}
		}*/
		first_id_set.push(next_destination_id);
	}

	
		

	//_write_console(JSON.stringify(new_ids));
	//_write_console('First ID Set: '+JSON.stringify(first_id_set));

	for (var i in json_addresses.rows) {
		if (i != van_id && json_addresses.rows[i].van == van) { // non-van
			command += _partial_command(i, first_id_set);
		}
	}

	_write_console("&gt;&gt;&gt; " + command);
	

	optimize_route(command, can_update, first_id_set);
}

// given python command, pass command to optimize.php which calls exec(command)
function optimize_route(command, can_update, first_id_set) {

	var xmlhttp;

	var python_start_time;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var route = xmlhttp.responseText.trim();
			
			_write_console(route);
			var python_timing = new Date().getTime() - python_start_time;
			_write_console("[Took "+python_timing/1000.0 + " s]");
			if (route == "0" && json_addresses.length > 1) {
				alert('Python returned 0. Maybe something bad happened?');
				_write_console("<strong>WARNING: Python maybe didn't like this command: "+command+"</strong>");
			}

			if (route != "Error") {
				if (first_id_set.length != 0) 
					route = first_id_set[0] + "," + route;
				_write_console("Modified route: "+route);
				cache_route(route);
			}
		}
	}
	
	var url = "../shared/optimize.php";
	var params = "cmd="+encodeURIComponent(command);
	
	xmlhttp.open("POST", url);
	xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xmlhttp.send(params);

	python_start_time = new Date().getTime();
}

function cache_route(routeText) {
	
	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			_write_subconsole('Wrote python cache.');
			//check_refresh(); // ???
			unlock('lock_van', van);
		}
	}

	var params = "cache="+encodeURIComponent(routeText)+"&fname=route_cache_"+van;
	
	xmlhttp.open("POST","write_cache.php");
	xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xmlhttp.send(params);
}

// display location and respective controls in route order as computed by python script
function load_locations(routeList, can_update) {

	var output = "<table>";

	next_destination_id = undefined;
	var is_van = false;

	for (var i = 0, i_distinct = 0; i < routeList.length; i++, i_distinct++) {

		if (i_distinct == 1)
			next_destination_id = parseFloat(routeList[i]);

		var locType = '';
		var pickup_destinations = '';

		var i_list = [];
		var id_list = [];

		is_van = i == 0;

		// is_disabled: true if first location (van)
		// or if any aggregate location is a dropoff with an assoc id
		var is_disabled = is_van;

		// aggregate identical locations
		do {

			var tmp_id = parseFloat(routeList[i]);

			i_list.push(i);
			id_list.push(tmp_id);

			is_disabled = is_disabled || (json_addresses.rows[tmp_id].type == 'DROPOFF' && json_addresses.rows[tmp_id].assoc_id != null);

			locType += json_addresses.rows[tmp_id].type.toLowerCase() 
					+ ' ('+json_addresses.rows[tmp_id].num_people+'), ';
			if (json_addresses.rows[tmp_id].type == 'PICKUP') {
				if (pickup_destinations != '')
					pickup_destinations += "; ";
				var assoc = json_addresses.rows[tmp_id].assoc_id;
				if (assoc != undefined)
					pickup_destinations += json_addresses.rows[assoc].address;
			}

			if (is_van) break;

		} while(i+1 < routeList.length 
			&& json_addresses.rows[tmp_id].address == json_addresses.rows[routeList[i+1]].address && ++i);

		locType = locType.slice(0, -2); // extra trailing ", "

		var marker_color = _marker_color(id_list[0]);
		var letter = String.fromCharCode(65 + i_distinct);

		var icon = "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=" + letter + marker_color

		
		var extra = is_disabled ? " class=\"inactive\"" : '';
		var extra_button = is_disabled ? " class=\"disabled\" disabled=\"disabled\"" : '';
		var warning = i_list[0] == 1 ? '' : " (Are you ignoring the route I made for you?)";

		output += "<tr"+extra+">";
		output += "<td><img style=\"padding-right:5px;\" src=\"" + icon + "\"/></td><td><span class=\"header\">" 
			+ json_addresses.rows[id_list[0]].address + " </span></td><td> " + JSON.stringify(id_list) + "</td></tr>";
		output += "<tr"+extra+"><td>&nbsp;</td><td><span class=\"subheader\">" 
				+ locType + "</span></td></tr>";

		if (!is_van) output += "<tr"+extra+"><td>&nbsp;</td><td>Wait: "+(parseFloat(json_addresses.rows[id_list[0]].total_wait)
			+ (Math.round(+new Date()/1000) - json_addresses.rows[id_list[0]].last_updated))+"</td></tr>";
		if (pickup_destinations != '') {
			output += "<tr"+extra+"><td>&nbsp;</td><td>Destinations: "+pickup_destinations+"</td></tr>"
		}
		output += "<tr><td>&nbsp;</td><td><button "+extra_button
			+" onclick=\"arriveAtLocation("+JSON.stringify(i_list)+", "+JSON.stringify(id_list)+", this)\">Arrive</button</td></tr>";
		
		output += "<tr><td>&nbsp;</td><td><hr/></td></tr>";
	}

	output += "</table>";

	document.getElementById('locations_panel').innerHTML = output;
	_enable_controls();
	if (can_update) {
		unlock('lock_van', van);
	} else {
		unlock('lock_refresh', '');
	}
}

// plot route on map and directions on panel
function plot_route(routeList) {

	var waypts = [];

	for (var i = 0; i < routeList.length; i++) {
		waypts.push({
			location:json_addresses.rows[parseFloat(routeList[i])].address,
			stopover:true});
	}

	directionsService.route(waypts, routeList, google.maps.DirectionsTravelMode.DRIVING, 
							false, false, false, 'mi');
}

// button callbacks below 

// locks: list of lock objects that specify "van" and "name" attributes
// if any lock in locks is currently locked, then the caller's request is denied
// and the request is queued until it succeeds
function read_locks(caller, check_locks) {

	_write_console("<strong>read_locks: deprecated function</strong>");
	return;

	_write_subconsole('reading locks (from '+caller.toString().substring(0,caller.toString().indexOf('{')-1)+')');

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	var rand = Math.random();

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {

			_write_console("["+rand+"] Current locks in place: "+xmlhttp.responseText);

			var current_locks = JSON.parse(xmlhttp.responseText);

			for (var i = 0; i < check_locks.length; i++) {
				for (var id in current_locks.rows) {

					// if one lock is present, everything is locked
					if (check_locks[i].name == current_locks.rows[id].name 
						//&& (check_locks[i].van == '' || check_locks[i].van == current_locks.rows[id].van)) {
						//&& (check_locks[i].van == '' || check_locks[i].van != current_locks.rows[id].van)) {
						) {

						var read_locks_timeout = setTimeout(function(){read_locks(caller, check_locks);}, 5000); // locked; schedule a retry

						_write_subconsole('currently locked (from '+caller.toString().substring(0,caller.toString().indexOf('{')-1)+'); will try again soon: '+read_locks_timeout);
						return;
					}
				}
			}
			
			_write_console("["+rand+"] locks are open. ");
			caller(true);
		}
	}

	var url = "read_lock.php";

	xmlhttp.open("GET", url);
	xmlhttp.send();
}

// add location(s) into json_addresses
// this happens before DB insertion
function insertJSONLocation(insert_lock_opened, refreshed) {

	// close autocomplete if open
	$('#pickup, #dropoff').autocomplete('close');

	var new_pickup = document.getElementById('pickup').value.trim();
	var new_dropoff = document.getElementById('dropoff').value.trim();
	var new_size = document.getElementById('size').value.trim();

	// god damn it
	var hack = 0;

	// god damn it
	for (var id in json_addresses.rows) {
		if (json_addresses.rows[id].van == van) {
			hack++;
		}
	}

	//_write_console('hack: '+hack+'; van_id: '+van_id);
	//_write_console(JSON.stringify(json_addresses));

	hack = hack != 1; // lol

	//_write_console('HACK: '+hack);

	if (new_pickup == '' && new_dropoff == '') {
		alert('No locations entered? Cool...');
		return;
	}

	_disable_controls();
	document.getElementById('addRequest').value = "Adding...";

	if (!insert_lock_opened) {
		// cannot update unless both lock_refresh and current van lock are unlocked
		//read_locks(function(){lock(function(){read_locks(function(insert_lock_opened){insertJSONLocation(insert_lock_opened)}, [{"name": "lock_refresh", "van": ""}])}, 'lock_van', van)}, [{"name": "lock_van", "van": van}])
		/*
		read_locks(function(){lock(function(){read_locks(function(insert_lock_opened){
						insertJSONLocation(insert_lock_opened)}, [{"name": "lock_refresh", "van": ""}])}, 
						'lock_van', van)}, [{"name": "lock_van", "van": van}]);
		*/
		// callback, checks[, name, van]
		check_set_lock(function(){check_set_lock(function(insert_lock_opened){insertJSONLocation(insert_lock_opened)}, 
												[{"name": "lock_refresh", "van": ""}])}, 
						[{"name": "lock_van", "van": van}], 
						"lock_van",
						van);
		_write_subconsole("going to check lock for insert now.");
		return;
	} else {
		_write_subconsole("lock opened for insert!");
	}

	// need to make sure we're up to date before performing this operation
	if (!refreshed) {
		_write_console('Checking refresh before insert.');
		pending_operations.push(function(){insertJSONLocation(true, true)});
		check_refresh(true);
		return;
	} else {
		_write_console('Refreshed; time to insert. ');
	}

	if (new_size == '') {
		new_size = 1;
	}

	$.post("../shared/insert.php", // AJAX POST URL
		   {"pickup": new_pickup, "dropoff": new_dropoff, "size": new_size, "van": van}, // POST args
		   function(data) { // success callback
				var new_ids = JSON.parse(data);
				_write_console(new_ids[0]+", "+new_ids[1]);

				var has_pickup = new_pickup != '';
				var has_dropoff = new_dropoff != '';

				var pickup_id = new_ids[0]; // pickup id is always first
				var dropoff_id = new_ids[+has_pickup]; // position depends on whether pickup is provided

				if (has_pickup) {

					_write_console("pickup id: "+pickup_id);

					json_addresses.length++;
					json_addresses.rows[pickup_id] = {"id": pickup_id, "assoc_id": !has_dropoff? "None" : dropoff_id, "van": van, "type": "PICKUP", 
													  "address": new_pickup, "num_people": new_size, "total_wait": 0, 
													  "start_time": Math.round(+new Date()/1000), "last_updated": Math.round(+new Date()/1000)};
				}

				if (has_dropoff) {

					_write_console("dropoff id: "+dropoff_id);

					json_addresses.length++;
					json_addresses.rows[dropoff_id] = {"id": dropoff_id, "assoc_id": !has_pickup? "None" : pickup_id, 
													   "van": van, "type": "DROPOFF", "address": new_dropoff, 
													   "num_people": new_size*-1, "total_wait": 0, "start_time": Math.round(+new Date()/1000), 
													   "last_updated": Math.round(+new Date()/1000)};
				}

				if (has_pickup) {
					retrieve_local_geocode(new_pickup, canonicalize_location, [pickup_id, true, new_ids, hack]);
				}

				if (has_dropoff) {
					retrieve_local_geocode(new_dropoff, canonicalize_location, [dropoff_id, !has_pickup, new_ids, hack]);
				}
		   }
	);

/*
	var pickup_id = 0;
	var dropoff_id = 0;

	if (new_pickup != '') {

		pickup_id = _next_id(); 

		json_addresses.length++;
		json_addresses.rows[pickup_id] = {"id": pickup_id, "assoc_id": "None", "van": van, "type": "PICKUP", 
										  "address": new_pickup, "num_people": new_size, "total_wait": 0, 
										  "start_time": Math.round(+new Date()/1000), "last_updated": Math.round(+new Date()/1000)};

	}

	if (new_dropoff != '') {

		dropoff_id = _next_id(); 

		json_addresses.length++;
		json_addresses.rows[dropoff_id] = {"id": dropoff_id, "assoc_id": pickup_id == ''? "None" : pickup_id, 
										   "van": van, "type": "DROPOFF", "address": new_dropoff, 
										   "num_people": new_size*-1, "total_wait": 0, "start_time": Math.round(+new Date()/1000), 
										   "last_updated": Math.round(+new Date()/1000)};
	}

	if (new_pickup != '' && new_dropoff != '') {
		json_addresses.rows[pickup_id].assoc_id = dropoff_id;
	}

	var new_ids = [];
	if (new_dropoff != '')
		new_ids.push(dropoff_id);
	if (new_pickup != '')
		new_ids.push(pickup_id)

	if (new_dropoff != '') {
		retrieve_local_geocode(new_dropoff, canonicalize_location, [dropoff_id, new_pickup == '', new_ids, hack]);
	}

	if (new_pickup != '') {
		retrieve_local_geocode(new_pickup, canonicalize_location, [pickup_id, true, new_ids, hack]);
	}
*/
	document.getElementById('add_location').reset();
}

var refresh_id = '';

function check_set_lock(lock_callback, check_locks, name, van) {

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var result = xmlhttp.responseText.trim();
			var shit = ["Set", "Check"];
			if (!isNaN(parseFloat(result))) { // success
				refresh_id = parseFloat(result);
				_write_console(shit[+(name==undefined)]+" lock success: ("+name+", "+van+", "+refresh_id+")");
				lock_callback(true);
			} else {
				_write_console("<strong>"+result+"</strong>");
				_write_console(shit[+(name==undefined)]+" lock failure: ("+name+", "+van+", )");
				var read_locks_timeout = setTimeout(function(){check_set_lock(lock_callback, check_locks, name, van);}, 5000); // locked; schedule a retry
			}
		}
	}

	var url = "read_set_lock.php?check_locks="+encodeURIComponent(JSON.stringify(check_locks));
	if (name != undefined) {
		url += "&name="+name+"&van="+van;
	}

	xmlhttp.open("GET", url);
	xmlhttp.send();
}

function lock(lock_callback, name, van) {

	_write_console("<strong>lock: deprecated function</strong>");
	return;

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var result = xmlhttp.responseText;
			if (name == "lock_refresh")
				if (!isNaN(parseFloat(result))) {
					refresh_id = parseFloat(result);
					_write_console("Lock: ("+name+", "+van+", "+refresh_id+")");
				}
				else 
					_write_console("<strong>"+result+"</strong>");
			else if (isNaN(parseFloat(result)))
				_write_console("<strong>"+result+"</strong>");
			lock_callback();
		}
	}

	var url = "lock.php?name="+name+"&van="+van;

	xmlhttp.open("GET", url);
	xmlhttp.send();
}

function unlock(name, van) {

	// hack
	if (pending_operations.length > 0) {
		_write_console('From unlock '+name+': Found a pending operation in stack! Time to call it. ');
		var op = pending_operations.pop();
		op(true);
		return;
	}

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var result = xmlhttp.responseText.trim();
			_write_console("Unlock: ("+name+", "+van+", "+refresh_id+")");
			if (result != "")
				_write_console("<strong>"+result+"</strong>");
			if (refresh_timeout && !refresh_timeout_called) {
				alert('I was going to set a refresh timeout, but one is already set. How the hell did that happen?');
				_write_console('<strong>I was going to set a refresh timeout, but one is already set. How the hell did that happen?</strong>');
			} else {
				//refresh_timeout = setTimeout(function(){check_refresh();},5000);
				//refresh_timeout_called = false;
				//_write_console('refresh_timeout: '+refresh_timeout);
				$("#locations_container").mCustomScrollbar("update");
				$("#locations_container").mCustomScrollbar("scrollTo","top"); //scroll to top
				refreshing = false;
				check_refresh();
			}
		}
	}

	var url = "unlock.php?name="+name+"&van="+van+"&id="+refresh_id;


	xmlhttp.open("GET", url);
	xmlhttp.send();
}

// i is route order
// id gives location id in json_addresses
function arriveAtLocation(i_list, id_list, button, arrive_lock_opened, refreshed, has_changed) {

	if (has_changed) {
		_write_console('<strong>NOTE: Arrival canceled due to outdated cache; try again. </strong>');
		return;
	}

	var json_address = json_addresses.rows[id_list[0]];

	var warning = i_list[0] == 1 ? '' : '(Are you ignoring the route I made for you?)';
	var msg = "Arriving at " + json_address.address + ". " + warning;

	// do stuff: needs to be true in all cases unless confirm(msg) returns false
	// but confirm(msg) should only be called when !arrive_lock_opened
	do_stuff = !arrive_lock_opened ? confirm(msg) : true;

	if (do_stuff) {

		_disable_controls();
		button.innerHTML = 'Arriving...';

		if (!arrive_lock_opened) {
			// cannot update unless both lock_refresh and current van lock are unlocked
			//read_locks(function(){lock(function(){read_locks(function(arrive_lock_opened){arriveAtLocation(i_list, id_list, button, arrive_lock_opened)}, [{"name": "lock_refresh", "van": ""}])}, 'lock_van', van)}, [{"name": "lock_van", "van": van}])
			check_set_lock(function(){check_set_lock(function(arrive_lock_opened){arriveAtLocation(i_list, id_list, button, arrive_lock_opened)}, 
												[{"name": "lock_refresh", "van": ""}])}, 
						[{"name": "lock_van", "van": van}], 
						"lock_van",
						van);
			_write_subconsole("going to check lock for arrival now.");
			return;
		} else {
			_write_subconsole("lock opened for arrival!");
		}

		// need to make sure we're up to date before performing this operation
		// if not up to date, just cancel the operation
		if (!refreshed) {
			_write_console('Checking refresh before arrival.');
			pending_operations.push(function(has_changed){arriveAtLocation(i_list, id_list, button, arrive_lock_opened, true, has_changed)});
			check_refresh(true);
			return;
		} else {
			_write_console('Refreshed; time to insert. ');
		}

		var xmlhttp;

		if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
			xmlhttp=new XMLHttpRequest();
		} else {// code for IE6, IE5
			xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
		}

		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				var result = xmlhttp.responseText.trim();
				if (result != "") {
					alert("broken arrival: "+result);
					_write_console("<strong>WARNING: "+result+"</strong>");
				}
				_write_console("Arrived at "+json_address.address+".");
				
				//lock(function(){populateVars(true);}, 'lock_van', van);
				populateVars(true);
			}
		}

		//var assoc_id_list = [];
		var total_num_people = 0;
		var total_total_wait = 0;

		for (var x = 0; x < id_list.length; x++) {
			total_num_people += parseFloat(json_addresses.rows[id_list[x]].num_people);
			total_total_wait += parseFloat(json_addresses.rows[id_list[x]].total_wait);
		}

		var url = "arrive.php";
/*
		var params = "id="+encodeURIComponent(json_address.id)+
					"&assoc_id="+encodeURIComponent(json_address.id)+
					"&address="+encodeURIComponent(json_address.address)+
					"&num_people="+encodeURIComponent(total_num_people)+
					"&total_wait="+encodeURIComponent(json_address.total_wait)+
					"&van="+encodeURIComponent(json_address.van);
*/
		var params = "id="+encodeURIComponent(JSON.stringify(id_list))+
					"&assoc_id="+encodeURIComponent(JSON.stringify(id_list))+
					"&address="+encodeURIComponent(json_address.address)+
					"&num_people="+encodeURIComponent(total_num_people)+
					"&total_wait="+encodeURIComponent(total_total_wait)+
					"&van="+encodeURIComponent(json_address.van);

		xmlhttp.open("POST", url);
		xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		xmlhttp.send(params);
	}
}

function deleteLocations(delete_lock_opened) {

	var msg = "WARNING: All locations will be permanently deleted. ";
	// do stuff: needs to be true in all cases unless confirm(msg) returns false
	// but confirm(msg) should only be called when !delete_lock_opened
	do_stuff = !delete_lock_opened ? confirm(msg) : true;

	if (do_stuff) {

		_disable_controls();
		document.getElementById('deleteAll').innerHTML = "Deleting...";

		if (!delete_lock_opened) {
			// cannot update unless both lock_refresh and current van lock are unlocked
			//read_locks(function(){lock(function(){read_locks(function(delete_lock_opened){deleteLocations(delete_lock_opened)}, [{"name": "lock_refresh", "van": ""}])}, 'lock_van', van)}, [{"name": "lock_van", "van": van}])
			check_set_lock(function(){check_set_lock(function(delete_lock_opened){deleteLocations(delete_lock_opened)}, 
												[{"name": "lock_refresh", "van": ""}])}, 
						[{"name": "lock_van", "van": van}], 
						"lock_van",
						van);
			_write_subconsole("going to check lock for deletion now.");
			return;
		} else {
			_write_subconsole("lock opened for deletion!");
		}

		var xmlhttp;

		if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
			xmlhttp=new XMLHttpRequest();
		} else {// code for IE6, IE5
			xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
		}

		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				var result = xmlhttp.responseText;
				
				//document.getElementById("directions_panel").innerHTML = '';

				_write_subconsole("Deleted all locations.<br/>");
				
				// reset all 
				
				//initialize();
				//lock(function(){populateVars(true);}, 'lock_van', van);
				populateVars(true);
			}
		}

		var url = "truncate.php";
		var params = "remove="+encodeURIComponent("aq2743tgevFJV;");

		xmlhttp.open("POST", url);
		xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		xmlhttp.send(params);
	}
}

// utility functions below

function _write_console(data) {
	//var now = new Date();
	var now = new Date().toString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1"); // hh:mm:ss
	consoleList.push("["+now+"] " + data);
	while(consoleList.length > MAX_CONSOLE_LENGTH)
		consoleList.shift(); // drop data from front
	outputDiv.innerHTML = consoleList.join("<br/>");
	$("#outputDiv_container").mCustomScrollbar("update");
	$("#outputDiv_container").mCustomScrollbar("scrollTo","bottom"); //scroll to bottom
	//$("#outputDiv_container").scrollTop = $("#outputDiv_container").scrollHeight;
	//outputDiv_container.scrollTop = outputDiv_container.scrollHeight;
}

function _write_subconsole(data) {
	var now = new Date().toString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1"); // hh:mm:ss
	subconsoleList.push("["+now+"] " + data);
	while(subconsoleList.length > MAX_SUBCONSOLE_LENGTH)
		subconsoleList.shift(); // drop data from front
	suboutputDiv.innerHTML = subconsoleList.join("<br/>");
	$("#suboutputDiv_container").mCustomScrollbar("update");
	if (console_autoscroll)
		$("#suboutputDiv_container").mCustomScrollbar("scrollTo","bottom"); //scroll to bottom
}

// add request form action; add info to db
function _insertDBLocation(json_row) {

	_write_console("<strong>_insertDBLocation: deprecated function</strong>");
	return;

	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var result = xmlhttp.responseText.trim();
			if (result != '') {
				alert('shit! ['+result+']');
				_write_console("<strong>WARNING: "+result+"</strong>");
			}
			//_write_console('Inserted location: '+json_row.address + result);
			// update cache on the reload
		}
	}

	paramstr = "id="+json_row.id+
			  "&assoc_id="+json_row.assoc_id+
			  "&type="+json_row.type+
			  "&address="+json_row.address+
			  "&num_people="+json_row.num_people+
			  "&van="+json_row.van;

	xmlhttp.open("GET","../shared/insert.php?"+paramstr,true);
	xmlhttp.send();
}

function _delete_location_from_matrix(i) {
	var location = matrix_cache.originAddresses[i];
	matrix_cache.originAddresses.splice(i, 1);
	matrix_cache.destinationAddresses.splice(i, 1);
	for (var j = 0; j < matrix_cache.rows.length; j++)
		matrix_cache.rows[j].elements.splice(i, 1);
	matrix_cache.rows.splice(i, 1);
	//_write_console("Cache after removing "+location+": " + _response_table(matrix_cache));
}

// single argument to a python command
function _partial_command(id, first_id_set) {

	var command = '';
	
	var i = id_map[id];

	var results = matrix_cache.rows[i].elements;
	
	var loc_id = json_addresses.rows[id].id;
	var assocID = json_addresses.rows[id].assoc_id;
	var size = parseFloat(json_addresses.rows[id].num_people);

	// add van num_people to first location 
	// since van is excluded from call
	if (first_id_set.indexOf(parseFloat(id)) != -1) {
		size += parseFloat(json_addresses.rows[van_id].num_people); 
		assocID = 'None'; 
	}

	// account for arrival at first location
	if (first_id_set.indexOf(parseFloat(assocID)) != -1) {
		assocID = 'None';
	}

	/*
	if (size == 0 && id != van_id) {
		alert('['+id+'] has size 0? Bad things will probably happen soon...');
		_write_console('<strong>WARNING: Location ['+id+'] has size 0.</strong>');
	}*/

	var wait = parseFloat(json_addresses.rows[id].total_wait) + (Math.round(+new Date()/1000) - json_addresses.rows[id].last_updated); // ???
	
	command += "'"+loc_id+";"+ assocID +";"+size+";"+wait+";";

	for (var tmp_id in json_addresses.rows) {
		if (json_addresses.rows[tmp_id].van == van && (tmp_id != van_id || first_id_set.length == 0)) {
			command += tmp_id+"="+results[id_map[tmp_id]].duration.value+","; 
		}
	}

	command = command.slice(0,-1); // trailing comma
	command += "' ";

	return command;
}

function _response_table(resp) {

	str = "<table cellspacing=\"0\" style=\"border-left: 1px solid; border-top: 1px solid;\">"+
	"<tr><td style=\"border-right:1px solid; border-bottom:1px solid;padding:3px;\">&nbsp;</td>";
	
	for (var i = 0; i < resp.destinationAddresses.length; i++) {
		str += "<td style=\"border-right:1px solid; border-bottom:1px solid;padding:3px;\">"
				+resp.destinationAddresses[i]+"</td>";
	}

	str += "</tr>"

	for (var i = 0; i < resp.originAddresses.length; i++) {
		str += "<tr><td style=\"border-right:1px solid; border-bottom:1px solid;padding:3px;\">" 
				+ resp.originAddresses[i] + "</td>";
		for (var j = 0; j < resp.destinationAddresses.length; j++) {
			str += "<td style=\"border-right:1px solid; border-bottom:1px solid;padding:3px;\">" 
			+ JSON.stringify(resp.rows[i].elements[j]) +  "</td>";
		}
		str += "</tr>";
	}
	str += "</table>";
	return str;
}

// figure out the next_id in json_addresses. this is stupid; this should be maintained in a variable
function _next_id () { 

	if (json_addresses.length == 0)
		return 0;

	var nextid = 0;

	for (id in json_addresses.rows) {
			if (parseFloat(id) > nextid)
				nextid = id;
		}

	return parseFloat(nextid)+1;
}

// disable all controls; used during loading to prevent accidental multiple requests from the same person
function _disable_controls() {

	// test
	if (refresh_timeout) {
		clearTimeout(refresh_timeout);
		_write_subconsole('clearing refresh timeout: '+refresh_timeout);
		refresh_timeout = false;
	}


	busy = true;
	edit_cache  = true;

	var buttons = document.getElementsByTagName("button");
	for (var i = 0; i < buttons.length; i++) {
		buttons[i].disabled = true;
	}

	var inputs = document.getElementsByTagName("input");
	for (var i = 0; i < inputs.length; i++) {
		if (inputs[i].type == "submit") {
			inputs[i].disabled = true;
		}
	}
}

// restore the disabled controls after loading
function _enable_controls() {

	edit_cache = false;
	_write_console("Controls enabled. ");

	var buttons = document.getElementsByTagName("button");
	for (var i = 0; i < buttons.length; i++) {
		if (buttons[i].className != "disabled")
			buttons[i].disabled = false;
	}

	var inputs = document.getElementsByTagName("input");
	for (var i = 0; i < inputs.length; i++) {
		if (inputs[i].type == "submit") {
			inputs[i].disabled = false;
		}
	}

	document.getElementById('addRequest').value = "Add Request";
	document.getElementById('deleteAll').innerHTML = "Delete All";

	busy = false;
	//unlock();
}

//window.onbeforeunload = function(){unlock('lock_refresh', '')};

window.onresize = function() {
	set_heights();
}

function set_heights() {

	var body_height = body_container.offsetHeight;
	var title_height = title_container.offsetHeight;
	var insert_height = header_container.offsetHeight;
	var map_height = map_container.offsetHeight;
	var console_height = console_container.offsetHeight;
/*
	var body_height = document.getElementsByTagName("body")[0].offsetHeight;
	var title_height = document.getElementById('title_container').offsetHeight;
	var insert_height = document.getElementById('header_container').offsetHeight;
	var map_height = document.getElementById('map_canvas').offsetHeight;
	var console_height = document.getElementById('outputDiv_container').offsetHeight;
*/
	var new_height = (body_height - (title_height+insert_height+console_height))+'px';

	//document.getElementById('map_canvas').style.height = new_height;
	map_container.style.height = new_height;
	document.getElementById('locations_container').style.height = new_height;
	document.getElementById('directions_container').style.height = new_height;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////// Some code that follows is stolen from some website (heavily modified though). /////////////////
////////////// http://techquarries.blogspot.com/2010/08/using-more-than-8-waypoints-in-google.html //////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////





/**
 * Creates a DirectionsRoute object.
 *
 * @constructor
 * @param {DirectionsDisplay} directionsDisplay The display object.
 */
function DirectionsRoute(directionsDisplay) {
	this.directionsDisplay = directionsDisplay;
	this.directionsService = new google.maps.DirectionsService();
}

/**
 * Calculate path between destinations.
 *
 * @param {Array<google.maps.DirectionsWaypoint>} destinations The list of destinations
 *				to visit.
 * @param {google.maps.DirectionsTravelMode} selectedMode The type of traveling: car, bike, or walking
 * @param {bool} hwy Whether to avoid highways
 * @param {bool} toll Whether to avoid tolled roads
 * @param {bool} onlyCurrent If using multiple routes, do we want to show all of them or just the current
 * @param {string} units The distance units to use, either "km" or "mi"
 */
DirectionsRoute.prototype.route = function(destinations, routeList, selectedMode, hwy, toll, onlyCurrent, units) {
	this.directionsDisplay.reset();
	
	// Determine unit system.
	var unitSystem = google.maps.DirectionsUnitSystem.IMPERIAL;
	if(units == "km")
		unitSystem = google.maps.DirectionsUnitSystem.METRIC;

	for (var i = 0, i_distinct = 0; i < destinations.length; i++, i_distinct++) {
		// is this safe?
		while(i+1 < routeList.length 
			&& json_addresses.rows[routeList[i]].address == json_addresses.rows[routeList[i+1]].address) {
			i++;
		}

		this.directionsDisplay.add_marker_(destinations[i].location, routeList[i], String.fromCharCode(65+i_distinct));
	}

	var inc = 1;
	var emph = .8;

	// Loop through all destinations in groups of 10, and find route to display.
	
				// why destinations.length - 1? it's a mystery
	for(var idx1=0; idx1<destinations.length-1 || idx1 == 0; idx1+=inc) {

		if (idx1 > 0) inc = 9;

		// Setup options.
		var idx2 = Math.min(idx1+inc, destinations.length-1);
		var request = {
			avoidHighways: hwy,
			avoidTolls: toll,
			origin: destinations[idx1].location,
			destination: destinations[idx2].location,
			travelMode: google.maps.DirectionsTravelMode[selectedMode],
			unitSystem: unitSystem,
			waypoints: destinations.slice(idx1+1, idx2)
		};

		var last = idx1+inc>=destinations.length-1;

		if (last) { // horrible code. but don't change; or else. 
			this.directionsService.route(request, function (response, status) {
				if (status == google.maps.DirectionsStatus.OK) {
					this.directionsDisplay.parse(response, units, emph, true);

					emph = .5;
				} else {
					alert(status);
				}
			});
		} else {
			this.directionsService.route(request, function (response, status) {
				if (status == google.maps.DirectionsStatus.OK) {
					this.directionsDisplay.parse(response, units, emph, false);
					//emph -= emph > 0.1 ? 0.1 : 0;
					emph = .5;
				} else {
					alert(status);
				}
			});
		}
	}
}

/**
 * Creates a DirectionsDisplay object.
 *
 * @constructor
 * @param {google.maps.Map} map The map to display the directions.
 * @param {DivObject} pane The div pane to put the step-by-step directions.
 */
markersList = [];
cacheMarkersList = [];
function DirectionsDisplay(map, pane) {
	this.geocoder = new google.maps.Geocoder();
	this.legs = new Array();
	this.distances = new Array();
	this.overallDistance = 0;
	this.map = map;
	this.pane = pane;
	this.routeHTMLtext = '';
}

/**
 * Generates boxes for a given route and distance
 *
 * @param {google.maps.DirectionsResult} response The result of calculating
 *				directions through the destinations.
 */
DirectionsDisplay.prototype.parse = function (response, units, emph, last) {

	var routes = response.routes;
	
	// Loop through all routes and append

	for(var rte in routes) {

		var legs = routes[rte].legs;
		this.add_leg_(routes[rte].overview_path, emph);

		for(var leg in legs) {
			var steps = legs[leg].steps;
			
			// Compute overall distance and time for the trip.
			this.overallDistance += legs[leg].distance.value;
			this.overallTime += legs[leg].duration.value;
		}
	}

	// Set zoom and center of map to fit all paths, and display directions.
	this.fit_route_();
	this.create_stepbystep_(response, units, last);
}

/**
 * Clears all stored information about the previous directions.
 */
DirectionsDisplay.prototype.reset = function () {
	// Delete all markersList.
	for(var x in markersList) {
		markersList[x].setMap(null);
	}

	markersList.length = 0; // ???
	//cacheMarkersList.length = 0;
	
	this.routeHTMLtext = '';
	this.pane.innerHTML = '';
	
	// Delete all polylines.
	for(var x in this.legs) {
		this.legs[x].setMap(null);
	}
	this.legs = new Array();
	
	// Delete all stored distances.
	for(var x in this.distances) {
		this.distances[x].setMap(null);
	}
	this.distances = new Array();
	
	// Reset overall counters.
	this.overallDistance = 0;
	this.overallTime = 0;
}

function _marker_color(id) {
	
	var marker_color = "|70C276|1C4725"; // pickup color: green

	var type = json_addresses.rows[id].type; // sometimes error: cannot read property 'type' of undefined
	if (type == 'DROPOFF') {
		marker_color = "|6DA5D6|103161"; // dropoff color: blue
	} else if (type == 'VAN') {
		marker_color = "|eeeeee|555555"; // van color: white
	}

	return marker_color;
}

/**
 * Displays a marker on the map, whether it is an address or a lat/lon.
 *
 * @param {google.maps.LatLng | string} location The location to put the marker
 */
DirectionsDisplay.prototype.add_marker_ = function (location, id, letter) {
	if (isString(location))
		retrieve_local_geocode(location, add_marker, [this.map, letter, _marker_color(id)]);
	else 
		add_marker(location, [this.map]);
}

function retrieve_local_geocode(location, geocode_callback, callback_args) {
	/*
	var xmlhttp;

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var response = xmlhttp.responseText.trim();
			if (response != "") {
				_write_subconsole("Got response for local geocode query on "+location+": "+response);

				geocode_callback(response, callback_args);

			} else {
				retrieve_google_geocode(location, geocode_callback, callback_args);
			}
		}
	}

	var params = "input_address="+encodeURIComponent(location);
	
	xmlhttp.open("POST","retrieve_geocode.php");
	xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xmlhttp.send(params);
	*/
	$.post("retrieve_geocode.php",
		   {"input_address": location},
		   function(response) {
		   		response = response.trim();
				if (response != "") {
					_write_subconsole("Got response for local geocode query on "+location+": "+response);
					geocode_callback(response, callback_args);
				} else {
					retrieve_google_geocode(location, geocode_callback, callback_args);
				}
		   }
	);
}

function retrieve_google_geocode(location, geocode_callback, callback_args) {

	this.geocoder.geocode({address: location}, function (results, status) {
		if(status == google.maps.GeocoderStatus.OK) {
			
			var places = [results[0].formatted_address, results[0].geometry.location];
			geocode_callback(JSON.stringify({"lat":places[1].Xa, "lng":places[1].Ya, "full_address":places[0]}), callback_args);
			insert_geocode(location, places[0], JSON.stringify(places[1]));
			
		} else {
			alert('shit! a geocode failed ('+status+') for '+location);
		}
	});
}

function insert_geocode(location, full_address, position) {
	var xmlhttp;

	var tmp_latlng = JSON.parse(position); // position is stringified json position

	if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp=new XMLHttpRequest();
	} else {// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			var response = xmlhttp.responseText.trim();
			if (response != "")
				_write_console('<strong>'+response+'</strong>');
			else
				_write_console('Wrote '+location+' ('+full_address+') with lat(Xa):'+tmp_latlng.Xa+', lng(Ya):'+tmp_latlng.Ya+' to geocode database.');
		}
	}

	var params = "input_address="+encodeURIComponent(location)+
				 "&full_address="+encodeURIComponent(full_address)+
				 "&lat="+encodeURIComponent(tmp_latlng.Xa)+
				 "&lng="+encodeURIComponent(tmp_latlng.Ya);
	
	xmlhttp.open("POST","insert_geocode.php");
	xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xmlhttp.send(params);
}

function add_marker(response, callback_args) {

	var display_map = callback_args[0];

	if (isString(response)) {
		var entry = JSON.parse(response);
		var letter = callback_args[1];
		var marker_color = callback_args[2];

		var lat = entry.lat;
		var lng = entry.lng;

		window.markersList.push(new google.maps.Marker({
			position:  new google.maps.LatLng(lat, lng),
			map: display_map,
			icon: "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=" + letter + marker_color
		}));
	} else {

		if (car_marker != undefined)
			car_marker.setMap(null);

		car_marker = new google.maps.Marker({
			position: response,
			map: display_map,
			icon: "https://chart.googleapis.com/chart?chst=d_simple_text_icon_left&chld=|14|000|taxi|24|000|FFF"
		})

		window.markersList.push(car_marker);
	}
}

function canonicalize_location(response, callback_args) {

	var entry = JSON.parse(response);
	var canonical_name = entry.full_address;
	var index = callback_args[0];
	var continue_processing = callback_args[1];
	var new_ids = callback_args[2];

	var hack = callback_args[3];

	var json_row = json_addresses.rows[index];
	_write_subconsole("Canonicalize: "+json_row.address+" --> "+canonical_name);
	json_row.address = canonical_name;
	json_row.lat = entry.lat;
	json_row.lng = entry.lng;

	$.post("edit.php", 
		   {"json_address": JSON.stringify(json_row)}, 
		   function(response) {
		   		response = response.trim();
		   		if (response != "")
		   			_write_console("<strong>"+response+"</strong>");
		   		if (continue_processing) 
					read_matrix_cache(true, hack, new_ids);
		   }
	);
}

// Return a boolean value telling whether the first argument is a string. 
function isString(val) {
	if (typeof(val) == 'string') return true;
	if (typeof(val) == 'object') {
		var criterion = arguments[0].constructor.toString().match(/string/i); 
		return (criterion != null);
	}
	return false;
}

function componentToHex(c) {
	var hex = c.toString(16);
	return hex.length == 1 ? "0" + hex : hex;
}

function rgbToHex(r, g, b) {
	return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}

/**
 * Displays a PolyLine of points on the map.
 *
 * @param {Array<google.maps.LatLng>} path The path of the leg
 */
DirectionsDisplay.prototype.add_leg_ = function (path, emph) {
	
	this.legs.push(new google.maps.Polyline({
		path: path,
		map: this.map,
		strokeColor: "#1234dd"/*rgbToHex(20,40,(1-emph)*255)*/,
		strokeOpacity: emph,
		strokeWeight: 5}));
}

/**
 * Resize plot screen to fit route.
 */
DirectionsDisplay.prototype.fit_route_ = function () {
	// Go through all legs of route and fit plot.
	var latlngbounds = new google.maps.LatLngBounds();
	for(var leg in this.legs) {
		path = this.legs[leg].getPath();
		for(var i = 0; i < path.length; i++)
			latlngbounds.extend(path.getAt(i));
	}

	map.fitBounds(latlngbounds);
}

/**
 * Output step-by-step instructions to a div pane. Also setup mouse events.
 *
 * @param {maps.google.DirectionsResponse} response The response to Googles DirectionsService
 * @param {string} units The distance units to use, either "km" or "mi"
 */
DirectionsDisplay.prototype.create_stepbystep_ = function (response, units, last) {

	var htmlText = "<table id='tableDirections'>";

	var routes = response.routes;
	for(var rte in routes) {
		var legs = routes[rte].legs;
		for(var leg = 0; leg < legs.length; leg++) {

			if (legs[leg].distance.value == 0)
				continue;

			var steps = legs[leg].steps;
			var letter1 = legs[leg].start_address;//String.fromCharCode(65 + leg);
			var letter2 = legs[leg].end_address;//String.fromCharCode(65 + leg+1);
			htmlText += "<tr><th colspan=2><hr></th></tr>";
			htmlText += "<tr><th colspan=2 align='center'><span class=\"header\">" + letter1 + " to " 
						+ letter2 + "</span></th></tr>";
			var totalDist = 0;
			var totalDur = 0;

			//htmlText += "<tr><td>";
			for(var x = 0; x < steps.length; x++) {
			
				htmlText += "<tr id = 'step" + x + "'>";
				htmlText += "<td valign='top'><b>" + (x+1) + " </b></td>";
				htmlText += "<td>" + steps[x].instructions + "</td>";
				htmlText += "</tr>";
				htmlText += "<tr id='time" + x + "'>";
				htmlText += "<td> &nbsp;</td>"
				htmlText += "<td align='left'><i>Duration: " + steps[x].distance.text + ", " 
							+ steps[x].duration.text + "</i></td>";
				htmlText += "</tr>";
				totalDist += steps[x].distance.value;
				totalDur += steps[x].duration.value;
			}
			//htmlText += "</td></tr>";
			htmlText += "<tr><th colspan=2 align='left'>\Leg Distance: " 
						+ this.compute_distance_(totalDist, units) + "</th></tr>";
			htmlText += "<tr><th colspan=2 align='left'>\Leg Duration: " 
						+ this.secs_to_hrmins_(totalDur) + "</th></tr>";
		}
	}

	//this.pane.innerHTML += htmlText + "</table><br>" + response.routes[0].copyrights;
	this.routeHTMLtext += htmlText;

	if (last) {

		this.pane.innerHTML = "";

		this.pane.innerHTML += "Total Distance: " + this.compute_distance_(this.overallDistance, units);
		this.pane.innerHTML += "<br/>Total Time: " + this.secs_to_hrmins_(this.overallTime) + "<br/>";

		if(response.routes[0].warnings.length > 0) this.pane.innerHTML += "<br/>";
		for(var i = 0; i < response.routes[0].warnings.length; i++)
			this.pane.innerHTML += "<b><i>Warning: </i></b>" + response.routes[0].warnings[i] + "<br/>";

		this.pane.innerHTML += this.routeHTMLtext + "</table><br>" + response.routes[0].copyrights;
		this.routeHTMLtext = ''; // hack
		htmlText = ''; // hack?

		$("#directions_container").mCustomScrollbar("update");
		$("#directions_container").mCustomScrollbar("scrollTo","top"); //scroll to top
		/*
		if(navigator.geolocation) {
			if (!watching_position) {

				watching_position = true;

				var dd = this;
			
				navigator.geolocation.watchPosition(function(position){

					var lat = position.coords.latitude;
					var lng = position.coords.longitude;

					_write_console('updated position: ('+lat+', '+lng+')');

					var latlng = new google.maps.LatLng(lat, lng);

					dd.add_marker_(latlng);

					}, function(error){}, {
						enableHighAccuracy:true
					});
			}
		} else {
			alert("navigator.geolocation is not available");
		}*/
	}
}

/**
 * Convert distance from meters to either kilometers or miles for output.
 *
 * @param {number} distance The distance in meters
 * @param {string} units The distance units to use, either "km" or "mi"
 *
 * @return {string} The string containing the distance converted to appropriate coordinates
 */
DirectionsDisplay.prototype.compute_distance_ = function (distance, units) {
	if(units == "km")
		return Math.round((this.overallDistance/1000)*100)/100 + " km"
	else
		return Math.round(this.overallDistance*0.000621371192*100)/100 + " mi";
}

/**
 * Convert time from seconds to an hours-minutes format.
 *
 * @param {number} time The time in seconds
 *
 * @return {string} The string containing the time converted to appropriate units
 */
DirectionsDisplay.prototype.secs_to_hrmins_ = function (time) {
	var hrs = Math.floor(time/3600);
	var mins = Math.round(time/60 - hrs*60);
	if(hrs > 0 && mins > 0)
		return hrs + " hours " + mins + " minutes";
	else if(mins > 0)
		return mins + " minutes";
	else if(hrs > 0)
		return hrs + " hours";
	else
		return "0 minutes";
}


// set van location to current position and then call update_callback
function onPositionUpdate(position, update_callback) {
	var lat = position.coords.latitude;
	var lng = position.coords.longitude;
	_write_console("Current position: " + lat + " " + lng);

	geocoder.geocode({latLng: new google.maps.LatLng(lat, lng)}, function (results, status) {
		if(status == google.maps.GeocoderStatus.OK) {
			var places = [results[0].formatted_address, results[0].geometry.location];

			_write_console("Current position: " + places[0]);

			hack_van_row.address = places[0];

			var xmlhttp;

			if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
				xmlhttp=new XMLHttpRequest();
			} else {// code for IE6, IE5
				xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
			}

			xmlhttp.onreadystatechange = function() {
				if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
					var response = xmlhttp.responseText.trim();
					if (response != "")
						_write_console('<strong>'+response+'</strong>');

					hacked_van = true;
					//_write_console(JSON.stringify(hack_van_row));
					update_callback();
				}
			}

			var params = "json_address="+encodeURIComponent(JSON.stringify(hack_van_row));
			
			xmlhttp.open("POST","edit.php");
			xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			xmlhttp.send(params);
			
		} else {
			alert('shit! a geocode failed ('+status+') for current position');
		}
	});
}